package gee

import (
	"encoding/json"
	"fmt"
	"net/http"
)

// map[string]interface{}起了一个别名gee.H
// 构建JSON数据时，显得更简洁。
type H map[string]interface{}

/**
Context目前只包含了http.ResponseWriter和
http.Request，另外提供了对 Method 和 Path 这两个常用属性的直接访问。
中间件也不例外，接收到请求后，应查找所有应作用于该路由的中间件，保存在Context中，
依次进行调用。为什么依次调用后，还需要在Context中保存呢？
因为在设计中，中间件不仅作用在处理流程前，也可以作用在处理流程后，
即在用户定义的 Handler 处理完毕后，还可以执行剩下的操作。
*/
type Context struct {
	//	origin objects
	Writer http.ResponseWriter
	Req    *http.Request
	//	request info
	Path   string
	Method string
	Params map[string]string
	// response info
	StatusCode int
	// middleware
	handlers []HandlerFunc // 保存中间件的切片
	index    int           // 记录当前执行到了第几个中间件

	// engine pointer
	engine *Engine
}

func newContext(w http.ResponseWriter, r *http.Request) *Context {
	return &Context{
		Writer: w,
		Req:    r,
		Path:   r.URL.Path,
		Method: r.Method,
		index:  -1,
	}
}

// 通过调用该函数，中间可等待用户自定义的Handler处理结束后，
// 再做一些额外的操作
// 当在中间件中调用该方法时，控制权交给了下一个中间件
// 直到调用到最后一个中间件，然后再从后往前，
// 调用每个中间件在Next方法之后定义的部分
func (c *Context) Next() {
	c.index++
	s := len(c.handlers)
	for ; c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}

func (c *Context) Fail(code int, err string) {
	c.index = len(c.handlers)
	c.ToJSON(code, H{"message": err})
}

func (c *Context) Param(key string) string {
	value := c.Params[key]
	return value
}

func (c *Context) PostForm(key string) string {
	return c.Req.FormValue(key)
}

func (c *Context) GetQueryParmas(key string) string {
	return c.Req.URL.Query().Get(key)
}

func (c *Context) Status(code int) {
	c.StatusCode = code
	c.Writer.WriteHeader(code)
}

func (c *Context) SetHeader(key string, value string) {
	c.Writer.Header().Set(key, value)
}

func (c *Context) String(code int, format string, values ...interface{}) {
	c.SetHeader("Content-Type", "text/plain")
	c.Status(code)
	c.Writer.Write([]byte(fmt.Sprintf(format, values...)))
}

/**
提供了快速构造String/Data/ToJSON/HTML响应的方法
*/

// 转化为JSON格式
func (c *Context) ToJSON(code int, obj interface{}) {
	c.SetHeader("Content-type", "application/json")
	c.Status(code)
	encoder := json.NewEncoder(c.Writer)
	if error := encoder.Encode(obj); error != nil {
		http.Error(c.Writer, error.Error(), 500)
	}
}

func (c *Context) Data(code int, data []byte) {
	c.Status(code)
	c.Writer.Write(data)
}

//	返回HTML
func (c *Context) ToHTML(code int, name string, data interface{}) {
	c.SetHeader("Content-Type", "text/html")
	c.Status(code)
	if err := c.engine.htmlTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {
		c.Fail(500, err.Error())
	}
}
